namespace MoreLinq.Test;

using System;
using System.Collections.Generic;

static class SequenceReader
{
    public static SequenceReader<T> Read<T>(this IEnumerable<T> source)
    {
            if (source == null) throw new ArgumentNullException(nameof(source));
            return new SequenceReader<T>(source);
        }
}

/// <summary>
/// Adds reader semantics to a sequence where <see cref="IEnumerator{T}.MoveNext"/>
/// and <see cref="IEnumerator{T}.Current"/> are rolled into a single
/// "read" operation.
/// </summary>
/// <typeparam name="T">Type of elements to read.</typeparam>
/// <remarks>
/// Initializes a <see cref="SequenceReader{T}" /> instance
/// from an enumerator.
/// </remarks>
/// <param name="enumerator">Source enumerator.</param>
sealed class SequenceReader<T>(IEnumerator<T> enumerator) : IDisposable
{
    IEnumerator<T>? enumerator = enumerator ?? throw new ArgumentNullException(nameof(enumerator));

    /// <summary>
    /// Initializes a <see cref="SequenceReader{T}" /> instance
    /// from an enumerable sequence.
    /// </summary>
    /// <param name="source">Source sequence.</param>

    public SequenceReader(IEnumerable<T> source) :
        this(GetEnumerator(source)) { }

    static IEnumerator<T> GetEnumerator(IEnumerable<T> source)
    {
            if (source == null) throw new ArgumentNullException(nameof(source));
            return source.GetEnumerator();
        }

    IEnumerator<T> Enumerator =>
        this.enumerator ?? throw new ObjectDisposedException(GetType().FullName);

    /// <summary>
    /// Reads a value otherwise throws <see cref="InvalidOperationException"/>
    /// if no more values are available.
    /// </summary>
    /// <returns>
    /// Returns the read value;
    /// </returns>

    public T Read()
    {
            var e = Enumerator;

            if (!e.MoveNext())
                throw new InvalidOperationException();

            return e.Current;
        }

    /// <summary>
    /// Reads the end. If the end has not been reached then it
    /// throws <see cref="InvalidOperationException"/>.
    /// </summary>

    public void ReadEnd()
    {
            var enumerator = Enumerator;

            if (enumerator.MoveNext())
                throw new InvalidOperationException();
        }

    /// <summary>
    /// Disposes this object and enumerator with which is was
    /// initialized.
    /// </summary>

    public void Dispose()
    {
            var e = this.enumerator;
            if (e == null) return;
            this.enumerator = null;
            e.Dispose();
        }
}
